NSProxy 是什么?

NSProxy 是一个抽象根类。换句话说,

  1. 它没有父类;
  2. 它是一个抽象类,没有具体实现。

因此它常常作为其他对象或尚不存在对象的代理,定义对象的 API。

关于 NSProxy 的简单介绍

通常,当 proxy 收到消息时,它会将消息转发给实际的处理对象,或加载/转换为实际的消息处理对象进行处理。NSProxy 的子类可以用于实现透明的消息转发(例如 NSDistantObject),或者用于延迟资源消耗较大对象的创建时机。

NSProxy 实现了作为根类需要实现的基本方法(包括 NSObject 协议方法),同时作为抽象类,proxy 不能提供初始化方法,当收到未实现的消息时抛出异常。proxy 正确实现的子类,必须提供一个初始化或创建方法,并且重写 forwardInvocation:methodSignatureForSelector: 方法来处理它本身未实现的消息。

forwardInvocation: 将给定的调用传递给 proxy 实际代理的对象

methodSignatureForSelector: 默认抛出 NSInvalidArgumentException,重写这个方法可以为 proxy 代理对象返回一个合适的 NSMethodSignature 对象,

NSProxy 为什么可以解决 NSTimer 循环引用?

循环引用情况分析:VC -> timer -> VC(target), runloop -> timer

基于 NSProxy 消息转发的特性,让 NSProxy 子类弱持有 target,将收到的消息转发给实际的消息处理类,即 VC。

此时的引用情况: VC —> timer —> Proxy --> VC(target), runloop —> timer

循环引用此时便被打破了,这样做的 好处 是易拓展,低耦和,符合设计模式中的开闭原则和迪米特法则。因此 在一些开源框架中常常使用 proxy 持有一个弱引用的 target, 来打破 NSTimerCADisplayLinktarget 之间的循环引用。

解决的其它正确姿势:

iOS 10 之后 NSTimer 提供了 block 支持的API,而 iOS 10 之前循环引用的常见解决方式:

  1. VC 弱引用 timer,在 VCviewWillDisappear: 中调用 invalidate 废弃 timer
    引用情况为 VC --> timer —> VC(target), runloop —> timer

  2. 添加 NSTimer 分类 结合 Block 调用定时方法,VCdealloc 方法中调用 invalidate
    VC —> timer —> NSTimer(target 类方法执行 block), runloop —> timer

  3. 使用中间类进行解耦,中间类弱引用 target并重写消息转发方法 forwardInvocationVC 处理消息
    VC —> timer —> MiddleClass(消息转发给 target 处理) --> VC(target), runloop —> timer

    最后一点本质上和使用 NSProxy 相同,而使用 Proxy 代理的优点是:可以直接进行消息转发,和第三点相比跳过了消息派发和动态方法解析阶段,因此效率较高一些。

错误的解决方式:

1
2
__weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:3.0f target:weakSelf selector:@selector(doSomthing) userInfo:nil repeats:YES];

即使 weakSelf 并将 target 指向 weakSelftimer 还是会强引用 self。因为所有权修饰符无论是 __weak 还是 __strong ,在 NSTimer 中都会生成新的强引用指针重新指向,导致循环引用的。

apple 文档:The timer maintains a strong reference to target until it (the timer) is invalidated.

Proxy 怎么用?

YYImage 内 Proxy 比较规范的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
A proxy used to hold a weak object.
It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink.
*/
@interface _YYImageWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation _YYImageWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[_YYImageWeakProxy alloc] initWithTarget:target];
}
// 将消息转发给实际处理类
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
// 防止抛出异常
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
// 这里默认返回init 方法的签名,目的是防止抛出异常
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
// Proxy 其它的一些自省方法,可不实现
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end

参考文档